County Maps

The three maps below show maps of Fairfield County, South Carolina. To generate these maps, first scroll down to the section titled "Run These Cells First". Run all cells below that heading. Then, you may return to the top of this file and generate the three maps.

Average Household Size

In [91]:
m = folium.Map(location=[34.4, -81.1], zoom_start=10.5)

folium.Choropleth(
    geo_data=geojson,
    name='Household Size',
    data=cbg_data,
    columns=['census_block_group', 'household_size'], # , # ['State', 'Unemployment'],
    key_on='feature.properties.CensusBlockGroup',
    fill_color='Reds',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Average Household Size (# of people)'
).add_to(m)

marker_cluster = MarkerCluster(options={'maxClusterRadius': 10})
for building in buildings:
    add_marker(building, marker_cluster)
m.add_child(marker_cluster)

m
Out[91]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Median Household Income

In [92]:
m = folium.Map(location=[34.4, -81.1], zoom_start=10.5)

folium.Choropleth(
    geo_data=geojson,
    name='Median Household Income',
    data=cbg_data,
    columns=['census_block_group', 'B19013e1'], # B01001e1, # ['State', 'Unemployment'],
    key_on='feature.properties.CensusBlockGroup',
    fill_color='Greens',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Median Household Income ($)'
).add_to(m)

marker_cluster = MarkerCluster(options={'maxClusterRadius': 10})
for building in buildings:
    add_marker(building, marker_cluster)
m.add_child(marker_cluster)

m
Out[92]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Population

In [89]:
m = folium.Map(location=[34.4, -81.1], zoom_start=10.5)  # , tiles='Stamen Toner')

folium.Choropleth(
    geo_data=geojson,
    name='Population',
    data=cbg_data,
    columns=['census_block_group', 'B01001e1'], # , # ['State', 'Unemployment'],
    key_on='feature.properties.CensusBlockGroup',
    fill_color='Blues',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Population (# of people)'
).add_to(m)


marker_cluster = MarkerCluster(options={'maxClusterRadius': 10})
for building in buildings:
    add_marker(building, marker_cluster)
m.add_child(marker_cluster)
    
# marker_cluster.add_to(m)
    
m
Out[89]:
Make this Notebook Trusted to load map: File -> Trust Notebook

No Health Insurance

In [118]:
m = folium.Map(location=[34.4, -81.1], zoom_start=10.5)  # , tiles='Stamen Toner')

folium.Choropleth(
    geo_data=geojson,
    name='Population',
    data=cbg_data,
    columns=['census_block_group', 'percent_uninsured'],
    key_on='feature.properties.CensusBlockGroup',
    fill_color='Oranges',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Percentage With No Health Insurance (%)'
).add_to(m)


marker_cluster = MarkerCluster(options={'maxClusterRadius': 10})
for building in buildings:
    add_marker(building, marker_cluster)
m.add_child(marker_cluster)
    
# marker_cluster.add_to(m)
    
m
Out[118]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]:
 
In [86]:
def add_marker(building, marker_cluster):
    types = {
        'Apartments': {
            'icon': 'home',
            'color': 'orange'
        },
        'Nursing Home': {
            'icon': 'heart',
            'color': 'red'
        }
    }
    
    types_obj = types[building['type']]
    
    marker = folium.Marker(
        location=building['coordinates'],
        tooltip=f"<i>[{building['type']}]</i><br/>{building['name']}<br/>\n{building['address']}",
        icon=folium.Icon(color=types_obj['color'], icon=types_obj['icon'])
    ) # .add_to(map_obj)
    marker_cluster.add_child(marker)
    
    

Run Cells Below First:

(after this, you can run the cells above to generate the maps)

In [177]:
import numpy as np
import pandas as pd
import geopandas as gpd 
import matplotlib.pyplot as plt
import folium
from folium.plugins import MarkerCluster
from geopy.geocoders import Nominatim
import branca
import branca.colormap as cm

import math
In [36]:
locator = Nominatim(user_agent='myGeocoder')
location = locator.geocode('S-20-151, Fairfield, SC')
print(location, 'Latitude = {}, Longitude = {}'.format(location.latitude, location.longitude))
20, Redwood Road, Hanson, Rose Park, Salt Lake City, Salt Lake County, Utah, 84116, United States of America Latitude = 40.76892042845975, Longitude = -111.93893027676306
In [45]:
apartments = [
    {
        'name': 'Lamplighter Apartments',
        'address': 'Winnsboro Mills, SC 29180',
        'coordinates': (34.350296, -81.086537),
    },
    {
        'name': 'Deer Wood Apartments',
        'address': '647 US-321 BYP, Winnsboro, SC 29180',
        'coordinates': (34.361933, -81.098159),
    },
    {
        'name': 'Winnsboro Arms Apartments',
        'address': '61 Winnsboro Arms Dr, Winnsboro, SC 29180',
        'coordinates': (34.372125, -81.105137),
    },
    {
        'name': 'Gibson APT',
        'coordinates': (34.374366, -81.088088),
        'address': '308 Palmer St, Winnsboro, SC 29180',
    },
    {
        'name': 'Castlewood Apartments Phase I',
        'address': '200 Castlewood Dr, Winnsboro, SC 29180',
        'coordinates': (34.369261, -81.095067),
    },
    {
        'name': 'Laurelwood Aparrtments',
        'coordinates': (34.372969, -81.094324),
        'address': '16A Laurel Wood Ct, Winnsboro, SC 29180',
    },
]
In [72]:
nursing_homes = [
    {
        'name': 'PruittHealth - Ridgeway',
        'address': '213 Tanglewood Court, Ridgeway, SC 29130',
        'coordinates': (34.302030, -80.964782),
    },
    {
        'name': 'Blue Ridge in the Fields',
        'address': '117 Bellefield Rd, Ridgeway, SC 29130',
        'coordinates': (34.330878, -80.907010),
    },
    {
        'name': 'Ridgeway Manor Healthcare Center',
        'address': '117 Bellefield Rd, Ridgeway, SC 29130',
        'coordinates': (34.329630, -80.906810),
    },
]
In [73]:
buildings = []

for building in apartments:
    building['type'] = 'Apartments'
    buildings.append(building)

for building in nursing_homes:
    building['type'] = 'Nursing Home'
    buildings.append(building)
In [1]:
import os
from pathlib import Path
import json
import linecache
import functools

import pandas as pd
import geopandas as gpd 
import matplotlib.pyplot as plt
import folium 
from shapely.ops import nearest_points
from shapely.geometry import LineString
In [122]:
DATA_PATH = Path('/data/safegraph/safegraph_open_census_data')
PREPROCESSED_DATA_PATH = Path('../../../data/preprocessed/safegraph/safegraph_open_census_data')
In [123]:
# Only needs to be run once, to generate data slices
# This data is now included with the repo anyway -- so this does not need to be run any more

county_fips_code = '45039'  # Fairfield County, South Carolina

#!mkdir -p {PREPROCESSED_DATA_PATH}
#census_data_file_names = !ls {DATA_PATH}/data/ | grep [0-9] #  | cut -f 1 -d .  # eliminate .csv suffix
county_directory = PREPROCESSED_DATA_PATH / "data/county" / county_fips_code
#!mkdir -p {county_directory}
#for file_name in census_data_file_names:
#    !touch {county_directory}/{file_name}
#    print(county_directory/file_name)
#    !head -n 1 "{DATA_PATH}/data/{file_name}" > {county_directory}/{file_name}
#    !cat "{DATA_PATH}/data/{file_name}" | grep ^{county_fips_code}.*$ >> {county_directory}/{file_name}
In [99]:
# See: https://www.safegraph.com/blog/beginners-guide-to-census
    
table_ids = [
    'B01001e1',   # SEX BY AGE: Total: Total population -- (Estimate),Sex By Age, Total, Total Population -- (Estimate),,,,,
    #'B00001e1',   # UNWEIGHTED SAMPLE COUNT OF THE POPULATION: Total: Total population -- (Estimate),Unweighted Sample Count Of The Population, Total, Total Population -- (Estimate),,,,,
    #'B00001m1',   # UNWEIGHTED SAMPLE COUNT OF THE POPULATION: Total: Total population -- (Margin of Error),Unweighted Sample Count Of The Population, Total, Total Population -- (Margin Of Error),,,,,
    'B19013e1',   # Median Household Income
    #'B00002e1',   # UNWEIGHTED SAMPLE HOUSING UNITS: Total: Housing units -- (Estimate),Unweighted Sample Housing Units, Total, Housing Units -- (Estimate),,,,,
    #'B00002m1',   # UNWEIGHTED SAMPLE HOUSING UNITS: Total: Housing units -- (Margin of Error),Unweighted Sample Housing Units, Total, Housing Units -- (Margin Of Error),,,,,
    'B25001e1',   # HOUSING UNITS: Total: Housing units -- (Estimate),Housing Units, Total, Housing Units -- (Estimate),,,,,
    #'B25001m1',   # HOUSING UNITS: Total: Housing units -- (Margin of Error),Housing Units, Total, Housing Units -- (Margin Of Error),,,,,
    
    # 'B27010e35',  # TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 35 to 64 years: With one type of health insurance coverage: Civilian noninstitutionalized population -- (Estimate),Types Of Health Insurance Coverage By Age, 35 To 64 Years, With One Type Of Health Insurance Coverage, Civilian Noninstitutionalized Population -- (Estimate),,,,

    'B27010e17',  # TYPES OF HEALTH INSURANCE COVERAGE BY AGE: Under 18 years: No health insurance coverage: Civilian noninstitutionalized population -- (Estimate),Types Of Health Insurance Coverage By Age, Under 18 Years, No Health Insurance Coverage, Civilian Noninstitutionalized Population -- (Estimate),,,,
    'B27010e33',  # TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 18 to 34 years: No health insurance coverage: Civilian noninstitutionalized population -- (Estimate),Types Of Health Insurance Coverage By Age, 18 To 34 Years, No Health Insurance Coverage, Civilian Noninstitutionalized Population -- (Estimate),,,,
    'B27010e50',  # TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 35 to 64 years: No health insurance coverage: Civilian noninstitutionalized population -- (Estimate),Types Of Health Insurance Coverage By Age, 35 To 64 Years, No Health Insurance Coverage, Civilian Noninstitutionalized Population -- (Estimate),,,,
    'B27010e66',  # TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 65 years and over: No health insurance coverage: Civilian noninstitutionalized population -- (Estimate),Types Of Health Insurance Coverage By Age, 65 Years And Over, No Health Insurance Coverage, Civilian Noninstitutionalized Population -- (Estimate),,,,
    
    'C17002e1',   #RATIO OF INCOME TO POVERTY LEVEL IN THE PAST 12 MONTHS: Total: Population for whom poverty status is determined -- (Estimate),Ratio Of Income To Poverty Level In The Past 12 Months, Total, Population For Whom Poverty Status Is Determined -- (Estimate),,,,,
    
    # unavailable:
    # 'B17017e10',   # POVERTY STATUS IN THE PAST 12 MONTHS BY HOUSEHOLD TYPE BY AGE OF HOUSEHOLDER: Total: Households -- (Estimate),Poverty Status In The Past 12 Months By Household Type By Age Of Householder, Total, Households -- (Estimate),,,,,
]

cbg_field_desc = pd.read_csv(PREPROCESSED_DATA_PATH / 'metadata/cbg_field_descriptions.csv')
cbg_field_desc[cbg_field_desc.table_id.isin(table_ids)]
Out[99]:
table_id field_full_name field_level_1 field_level_2 field_level_3 field_level_4 field_level_5 field_level_6 field_level_7 field_level_8
4 B01001e1 SEX BY AGE: Total: Total population -- (Estimate) Sex By Age Total Total Population -- (Estimate) NaN NaN NaN NaN NaN
3094 B19013e1 MEDIAN HOUSEHOLD INCOME IN THE PAST 12 MONTHS ... Median Household Income In The Past 12 Months ... Total Households -- (Estimate) NaN NaN NaN NaN NaN
4394 B25001e1 HOUSING UNITS: Total: Housing units -- (Estimate) Housing Units Total Housing Units -- (Estimate) NaN NaN NaN NaN NaN
6140 B27010e17 TYPES OF HEALTH INSURANCE COVERAGE BY AGE: Und... Types Of Health Insurance Coverage By Age Under 18 Years No Health Insurance Coverage Civilian Noninstitutionalized Population -- (... NaN NaN NaN NaN
6158 B27010e33 TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 18 ... Types Of Health Insurance Coverage By Age 18 To 34 Years No Health Insurance Coverage Civilian Noninstitutionalized Population -- (... NaN NaN NaN NaN
6177 B27010e50 TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 35 ... Types Of Health Insurance Coverage By Age 35 To 64 Years No Health Insurance Coverage Civilian Noninstitutionalized Population -- (... NaN NaN NaN NaN
6194 B27010e66 TYPES OF HEALTH INSURANCE COVERAGE BY AGE: 65 ... Types Of Health Insurance Coverage By Age 65 Years And Over No Health Insurance Coverage Civilian Noninstitutionalized Population -- (... NaN NaN NaN NaN
7016 C17002e1 RATIO OF INCOME TO POVERTY LEVEL IN THE PAST 1... Ratio Of Income To Poverty Level In The Past 1... Total Population For Whom Poverty Status Is Determi... NaN NaN NaN NaN NaN
In [113]:
county_files = ['cbg_b00.csv', 'cbg_b01.csv', 'cbg_b19.csv', 'cbg_b25.csv', 'cbg_b27.csv', 'cbg_c17.csv'] #  !ls {county_directory}

dfs = [pd.read_csv(county_directory / file, dtype={'census_block_group': str}) for file in county_files]
merged = dfs[0]
for df in dfs[1:]:
    merged = pd.merge(merged, df, on=['census_block_group'])
cbg_data = merged
    
#combiner = lambda left, right: pd.merge(left, right, on=['census_block_group'])
#cbg_data = functools.reduce(combiner, dfs[0], dfs[1:])


'''cbg_b19 = pd.read_csv(county_directory / 'cbg_b19.csv', dtype={'census_block_group': str})
cbg_b01 = pd.read_csv(county_directory / 'cbg_b01.csv', dtype={'census_block_group': str})
cbg_data = pd.merge(cbg_b01, cbg_b19, on=['census_block_group'])'''
cbg_data = cbg_data[['census_block_group'] + table_ids]
#cbg_data.dropna().head()
cbg_data
Out[113]:
census_block_group B01001e1 B19013e1 B25001e1 B27010e17 B27010e33 B27010e50 B27010e66 C17002e1
0 450399601001 1101 29250.0 624 0 67 125 0 1101
1 450399601002 931 39931.0 523 29 0 62 0 931
2 450399602001 1015 46898.0 517 54 66 108 0 1015
3 450399602002 1260 30481.0 782 0 123 79 0 1260
4 450399602003 1467 48125.0 642 0 52 185 0 1409
5 450399603001 1046 68902.0 1079 0 0 0 0 1046
6 450399603002 890 NaN 449 82 0 167 0 890
7 450399603003 1482 30625.0 678 61 118 160 0 1387
8 450399603004 2257 31346.0 982 4 170 126 0 2132
9 450399604001 962 26250.0 451 0 30 73 0 933
10 450399604002 822 NaN 462 0 33 0 0 822
11 450399604003 660 70050.0 465 23 18 15 0 660
12 450399604004 815 21024.0 550 0 106 95 0 815
13 450399604005 2115 20601.0 938 107 314 174 0 2070
14 450399605001 2015 57375.0 609 0 132 144 0 2015
15 450399605002 1236 48722.0 538 0 58 50 0 1236
16 450399605003 1596 33125.0 684 0 149 105 0 1596
17 450399605004 1355 31306.0 689 0 36 33 0 1355
In [114]:
sum(cbg_data['B01001e1'])  # total population
Out[114]:
23025
In [115]:
cbg_data['household_size'] = cbg_data['B01001e1'] / cbg_data['B25001e1']
<ipython-input-115-a1e05c5d3218>:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  cbg_data['household_size'] = cbg_data['B01001e1'] / cbg_data['B25001e1']
In [116]:
cbg_data['num_uninsured'] = sum([cbg_data[col_id] for col_id in ['B27010e17', 'B27010e33', 'B27010e50', 'B27010e66']])
cbg_data['percent_uninsured'] = cbg_data['num_uninsured'] * 100.0 / cbg_data['B01001e1']
<ipython-input-116-f8b89c25760a>:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  cbg_data['num_uninsured'] = sum([cbg_data[col_id] for col_id in ['B27010e17', 'B27010e33', 'B27010e50', 'B27010e66']])
<ipython-input-116-f8b89c25760a>:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  cbg_data['percent_uninsured'] = cbg_data['num_uninsured'] * 100.0 / cbg_data['B01001e1']
In [220]:
cbg_data
Out[220]:
census_block_group B01001e1 B19013e1 B25001e1 B27010e17 B27010e33 B27010e50 B27010e66 C17002e1 household_size num_uninsured percent_uninsured
0 450399601001 1101 29250.0 624 0 67 125 0 1101 1.764423 192 17.438692
1 450399601002 931 39931.0 523 29 0 62 0 931 1.780115 91 9.774436
2 450399602001 1015 46898.0 517 54 66 108 0 1015 1.963250 228 22.463054
3 450399602002 1260 30481.0 782 0 123 79 0 1260 1.611253 202 16.031746
4 450399602003 1467 48125.0 642 0 52 185 0 1409 2.285047 237 16.155419
5 450399603001 1046 68902.0 1079 0 0 0 0 1046 0.969416 0 0.000000
6 450399603002 890 NaN 449 82 0 167 0 890 1.982183 249 27.977528
7 450399603003 1482 30625.0 678 61 118 160 0 1387 2.185841 339 22.874494
8 450399603004 2257 31346.0 982 4 170 126 0 2132 2.298371 300 13.291981
9 450399604001 962 26250.0 451 0 30 73 0 933 2.133038 103 10.706861
10 450399604002 822 NaN 462 0 33 0 0 822 1.779221 33 4.014599
11 450399604003 660 70050.0 465 23 18 15 0 660 1.419355 56 8.484848
12 450399604004 815 21024.0 550 0 106 95 0 815 1.481818 201 24.662577
13 450399604005 2115 20601.0 938 107 314 174 0 2070 2.254797 595 28.132388
14 450399605001 2015 57375.0 609 0 132 144 0 2015 3.308703 276 13.697270
15 450399605002 1236 48722.0 538 0 58 50 0 1236 2.297398 108 8.737864
16 450399605003 1596 33125.0 684 0 149 105 0 1596 2.333333 254 15.914787
17 450399605004 1355 31306.0 689 0 36 33 0 1355 1.966618 69 5.092251
In [9]:
# Census Block Groups
In [10]:
def geojson_for_county(state_abbreviation="SC",
                       county_name="Fairfield County",
                       county_fips_code='45039',
                       data_path=DATA_PATH):
    '''
    Will only run if you have the full geometry/cbg.geojson file from the SafeGraph census dataset.
    Otherwise, don't run this function: call
    '''
    
    path_suffix = 'geometry/cbg.geojson'
    
    if county_fips_code == '45039':
        file_name = PREPROCESSED_DATA_PATH / 'geometry/fips' / county_fips_code / 'cbg.geojson'
        with open(file_name, 'r') as f:
            return json.loads('\n'.join(f.readlines()))
    
    else:
        geojson_path = data_path / path_suffix

        header = !head -n 5 {geojson_path}
        footer = !tail -n 2 {geojson_path}

        # lines to search file for county of interest.
        # must be found by inspection using "tail | head" method below, and checking whether 
        # the state of interest is included.
        # If not included, search up or down via binary search (file is sorted by state)
        # TODO: write the binary search explicitly here, if we need to generalize to other states/counties
        line_start_search = 170000
        line_end_search = 180000
        num_lines = line_end_search - line_start_search

        # stream = os.popen(f"""< {geojson_path} tail -n +{line_start_search} | head -n {num_lines} | grep '"State": "{state_abbreviation}", "County": "{county_name}"'  """)
        #stream = os.popen(f"""cat {geojson_path} | tail -n +{line_start_search} | head -n {num_lines} | grep '"State": "{state_abbreviation}", "County": "{county_name}"'  """)
        #county_cbgs = stream.readlines()

        county_cbgs = [linecache.getline(str(geojson_path), line_number).strip() for line_number in range(line_start_search, line_end_search)]
        county_cbgs = [line for line in county_cbgs if f'"State": "{state_abbreviation}", "County": "{county_name}"' in line]

        print(len(county_cbgs))

        # remove final character from last entry in list:
        # a trailing "," that will mess up the JSON parsing
        if county_cbgs[-1][-1] == ',':
            county_cbgs[-1] = county_cbgs[-1][:-1]

        geojson = '\n'.join(header + county_cbgs + footer)
        with open(PREPROCESSED_DATA_PATH / 'geometry/fips/' / county_fips_code / 'cbg.geojson', 'w') as f:
            f.write(geojson)
        return json.loads(geojson)
    
In [11]:
# only needs to be run once
geojson = geojson_for_county()
In [12]:
len([f['properties']['CensusBlockGroup'] for f in geojson['features']])
#[f['properties']['CensusBlockGroup'] for f in cbgs_json['features']]
Out[12]:
18
In [13]:
#geojson['features'][0]['properties']

Property Values

In [124]:
PROPERTY_DATA_PATH = Path('../../../data/preprocessed/property_assessor/fips/') / county_fips_code
In [136]:
property_values_df = pd.read_csv(PROPERTY_DATA_PATH / 'property_data_detailed.csv')
In [139]:
parcel_ids = list(property_values_df['ParcelId'])
In [140]:
property_values_df = property_values_df.set_index('ParcelId')

Property Values

Size of each dot represents the area of each land parcel.

Color of the dot represents the parcel's value, as determined by public tax assessment records.

Click on a dot to get more information.

In [280]:
m = folium.Map(location=[34.4, -81.1], zoom_start=10.5)  # , tiles='Stamen Toner')


marker_cluster = MarkerCluster(options={'maxClusterRadius': 10})

home_values = property_values_df[property_values_df.tax_market_value.notnull()]['tax_market_value']
high_home_value = 200000 # home_values.quantile(.8)
low_home_value = 50000 # home_values.quantile(.2)

folium.Choropleth(
    geo_data=geojson,
    name='Median Household Income',
    data=cbg_data,
    columns=['census_block_group', 'B19013e1'], # B01001e1, # ['State', 'Unemployment'],
    key_on='feature.properties.CensusBlockGroup',
    fill_color='Blues',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='Median Household Income ($)'
).add_to(m)


colormap = cm.LinearColormap(
    colors=[(255, 0, 0),
            (255, 63, 63),
            (255, 127, 127),
            (255, 191, 191),
            (255, 255, 255)],
    index=[
        #home_values.quantile(0.0),
        home_values.quantile(0.1),
        home_values.quantile(0.3),
        home_values.quantile(0.50),
        home_values.quantile(0.75),
        home_values.quantile(0.85),
        # home_values.quantile(1.00),
    ],
    vmin=home_values.quantile(0.1),
    vmax=home_values.quantile(0.85)
)
colormap.caption = 'Home Value ($)'

for parcel_id in parcel_ids: # int(len(parcel_ids)/2)]:  # [2:3]
    record = property_values_df.loc[parcel_id]
    lat, lon = record['latitude'], record['longitude']
    # record_data = eval(record['data'])
    if (lat is not None) and (lon is not None) and (not np.isnan(lat)) and (not np.isnan(lon)) and not np.isnan(record['tax_market_value']):
        '''folium.Circle(
            radius=100,
            location=[45.5244, -122.6699],
            popup='The Waterfront',
            color='crimson',
            fill=False,
        ).add_to(m)'''
        #print(lat)
        #print(lon)
        radius = 50
        area_sq_ft = record['area_sq_ft']
        if (area_sq_ft is not None) and (not np.isnan(area_sq_ft)):
            radius = math.sqrt(area_sq_ft / math.pi / 25)
        
        record_url = 'https://beacon.schneidercorp.com/Application.aspx?AppID=796&LayerID=11834&PageTypeID=4&PageID=5738&KeyValue='
        map_url = 'https://beacon.schneidercorp.com/Application.aspx?AppID=796&LayerID=11834&PageTypeID=1&PageID=5735&KeyValue='
        
        #owner = '[unknown]'
        #if 'owner' in record_data:
        #    owner = record_data['owner']
            #if 'name' in record_data['owner']:
            #    owner = record_data['owner']['name']
        tooltip_string = f'''
            ${record['tax_market_value']:,} <br />
            [<a href='{record_url}{parcel_id}'>Tax Record</a>] <br />
            [<a href='{map_url}{parcel_id}'>Map</a>] <br />
            
        '''
        
        marker = folium.Circle(
            radius=radius,
            location=(lat, lon),
            tooltip=tooltip_string + '(Click map marker to access links)',
            popup=tooltip_string,
            color=colormap(record['tax_market_value']),
            fill=True,
            fill_opacity=1.0
            # icon=folium.Icon(color=types_obj['color'], icon=types_obj['icon'])
        ) # .add_to(map_obj)
        marker.add_to(m)
        
        # marker_cluster.add_child(marker)

m.add_child(colormap)
m.add_child(marker_cluster)

folium.LayerControl().add_to(m)

m
Out[280]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [201]:
home_values.quantile(.99)
Out[201]:
557499.9999999997
In [208]:
home_values.quantile(.2)
Out[208]:
30600.0
In [204]:
len(property_values_df)
Out[204]:
17244
In [203]:
len(home_values)
Out[203]:
6431
In [150]:
x = float('nan')
In [190]:
 
Out[190]:
60300.0
In [158]:
np.isnan(property_values_df.loc[parcel_ids[0]]['latitude'])
Out[158]:
True
In [162]:
property_values_df.loc['002-00-00-034-000'][['latitude', 'longitude']]
Out[162]:
latitude     34.7507
longitude   -80.7651
Name: 002-00-00-034-000, dtype: object
In [163]:
np.isnan(None)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-163-f53008f0bd14> in <module>
----> 1 np.isnan(None)

TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
In [270]:
property_values_df
Out[270]:
data latitude longitude area_sq_ft standardized_land_use_category frontage_ft depth_ft building_count tax_assessed_value tax_market_value estimated_market_value standardized_land_use_type frontage depth
ParcelId
216-00-00-005-000 {'error': {'code': 'ISE01', 'status_code': 500... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
215-00-00-074-000 {'data': {'metadata': {'publishing_date': '202... 34.228086 -81.073488 1151726.0 RESIDENTIAL NaN NaN NaN 8028.0 311400.0 NaN SINGLE FAMILY RESIDENTIAL NaN NaN
215-00-00-102-000 {'data': {'metadata': {'publishing_date': '202... 34.230534 -81.075607 76666.0 RESIDENTIAL NaN NaN NaN 8260.0 206500.0 249000.0 SINGLE FAMILY RESIDENTIAL NaN NaN
026-04-02-005-000 {'data': {'metadata': {'publishing_date': '202... 34.564200 -80.914500 69696.0 RESIDENTIAL NaN NaN NaN 3968.0 NaN NaN SINGLE FAMILY RESIDENTIAL NaN NaN
026-04-02-007-000 {'data': {'metadata': {'publishing_date': '202... 34.564200 -80.914500 43560.0 RESIDENTIAL NaN NaN NaN 5500.0 NaN NaN SINGLE FAMILY RESIDENTIAL NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
141-00-00-031-000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
077-01-00-002-000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
048-00-00-002-000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
047-00-00-047-000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
114-01-01-021-000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

17244 rows × 14 columns

In [266]:
eval(property_values_df.loc['126-01-08-001-000'].data)
Out[266]:
{'data': {'metadata': {'publishing_date': '2020-02-01'},
  'address': {'street_number': '403',
   'street_pre_direction': None,
   'street_name': 'FAIRFIELD',
   'street_suffix': 'ST',
   'street_post_direction': None,
   'unit_type': None,
   'unit_number': None,
   'formatted_street_address': '403 FAIRFIELD ST',
   'city': 'WINNSBORO',
   'state': 'SC',
   'zip_code': '29180',
   'zip_plus_four_code': None,
   'carrier_code': 'C003',
   'latitude': 34.384037,
   'longitude': -81.093361,
   'geocoding_accuracy': 'PARCEL CENTROID',
   'census_tract': '450399604.004025'},
  'parcel': {'apn_original': '1260108001000',
   'apn_unformatted': '1260108001000',
   'apn_previous': None,
   'fips_code': '45039',
   'frontage_ft': None,
   'depth_ft': None,
   'area_sq_ft': None,
   'area_acres': None,
   'county_name': 'FAIRFIELD',
   'county_land_use_code': 'GX',
   'county_land_use_description': 'SINGLE-FAMILY',
   'standardized_land_use_category': 'RESIDENTIAL',
   'standardized_land_use_type': 'SINGLE FAMILY RESIDENTIAL',
   'location_descriptions': [],
   'zoning': 'R3',
   'building_count': None,
   'tax_account_number': '6523',
   'legal_description': '403 FAIRFIELD ST W8 BLDG SPLIT AND CARRIED ON 126-01-08-005',
   'lot_code': None,
   'lot_number': None,
   'subdivision': None,
   'municipality': None,
   'section_township_range': None},
  'structure': {'year_built': None,
   'effective_year_built': None,
   'stories': '1',
   'rooms_count': None,
   'beds_count': None,
   'baths': None,
   'partial_baths_count': None,
   'units_count': None,
   'parking_type': None,
   'parking_spaces_count': None,
   'pool_type': None,
   'architecture_type': None,
   'construction_type': None,
   'exterior_wall_type': None,
   'foundation_type': None,
   'roof_material_type': None,
   'roof_style_type': None,
   'heating_type': None,
   'heating_fuel_type': None,
   'air_conditioning_type': None,
   'fireplaces': None,
   'basement_type': None,
   'quality': None,
   'condition': 'AVERAGE',
   'flooring_types': [],
   'plumbing_fixtures_count': None,
   'interior_wall_type': None,
   'water_type': None,
   'sewer_type': None,
   'total_area_sq_ft': 12852,
   'other_areas': [{'type': 'UNCOVERED PORCH', 'sq_ft': '415'},
    {'type': 'GROSS AREA', 'sq_ft': '13267'}],
   'other_rooms': [],
   'other_features': [],
   'other_improvements': [],
   'amenities': []},
  'valuation': None,
  'taxes': [{'year': 2019,
    'amount': None,
    'exemptions': ['GOVERNMENT'],
    'rate_code_area': None}],
  'assessments': [],
  'market_assessments': [{'year': 2019,
    'land_value': None,
    'improvement_value': 610500,
    'total_value': 610500}],
  'owner': {'name': 'SCHOOL DISTRICT 14 OF FAIRFIELD COUNTY',
   'second_name': None,
   'unit_type': None,
   'unit_number': None,
   'formatted_street_address': None,
   'city': 'WINNSBORO',
   'state': 'SC',
   'zip_code': '29180',
   'zip_plus_four_code': None,
   'owner_occupied': None},
  'deeds': []},
 'metadata': {'timestamp': '2020-07-15T14:35:12.735158Z',
  'version': '0.8.5-2.1.0'},
 'warnings': []}
In [ ]: